#pragma once

#include "G3D/Vector3.h"

#include "spline.h"
#include "movement/move_spline_init_args.h"

using G3D::Vector3;

namespace Movement
{
    /**
     * @brief
     *
     */
    enum MonsterMoveType
    {
        MonsterMoveNormal = 0,
        MonsterMoveStop = 1,
        MonsterMoveFacingSpot = 2,
        MonsterMoveFacingTarget = 3,
        MonsterMoveFacingAngle = 4
    };

    /**
     * @brief
     *
     */
    struct Location : public Vector3
    {
        /**
         * @brief
         *
         */
        Location() : orientation(0) {}
        /**
         * @brief
         *
         * @param x
         * @param y
         * @param z
         * @param o
         */
        Location(float x, float y, float z, float o) : Vector3(x, y, z), orientation(o) {}
        /**
         * @brief
         *
         * @param v
         */
        Location(const Vector3& v) : Vector3(v), orientation(0) {}
        /**
         * @brief
         *
         * @param v
         * @param o
         */
        Location(const Vector3& v, float o) : Vector3(v), orientation(o) {}

        float orientation; /**< TODO */
    };

    /**
     * @brief MoveSpline represents smooth catmullrom or linear curve and point that moves belong it
     *
     * curve can be cyclic - in this case movement will be cyclic
     * point can have vertical acceleration motion componemt(used in fall, parabolic movement)
     *
     */
    class MoveSpline
    {
    public:
        /**
         * @brief
         *
         */
        typedef Spline<int> MySpline;
        /**
         * @brief
         *
         */
        enum UpdateResult
        {
            Result_None = 0x01,
            Result_Arrived = 0x02,
            Result_NextCycle = 0x04,
            Result_NextSegment = 0x08,
        };
        friend class PacketBuilder;
    protected:
        MySpline        spline; /**< TODO */

        FacingInfo      facing; /**< TODO */

        unsigned int          m_Id; /**< TODO */

        MoveSplineFlag  splineflags; /**< TODO */

        int           time_passed; /**< TODO */
        int           point_Idx; /**< TODO */
        int           point_Idx_offset; /**< TODO */

        /**
         * @brief
         *
         * @param args
         */
        void init_spline(const MoveSplineInitArgs& args);
    protected:

        /**
         * @brief
         *
         * @return const MySpline::ControlArray
         */
        const MySpline::ControlArray& getPath() const { return spline.getPoints(); }
        /**
         * @brief
         *
         * @param el
         */
        void computeFallElevation(float& el) const;

        /**
         * @brief
         *
         * @param ms_time_diff
         * @return UpdateResult
         */
        UpdateResult _updateState(int& ms_time_diff);
        /**
         * @brief
         *
         * @return int
         */
        int next_timestamp() const { return spline.length(point_Idx + 1); }
        /**
         * @brief
         *
         * @return int
         */
        int segment_time_elapsed() const { return next_timestamp() - time_passed; }
        /**
         * @brief
         *
         * @return int
         */
        int timeElapsed() const { return Duration() - time_passed; }
        /**
         * @brief
         *
         * @return int
         */
        int timePassed() const { return time_passed; }

    public:
        /**
         * @brief
         *
         * @return const MySpline
         */
        const MySpline& _Spline() const { return spline; }
        /**
         * @brief
         *
         * @return int
         */
        int _currentSplineIdx() const { return point_Idx; }
        /**
         * @brief
         *
         */
        void _Finalize();
        /**
         * @brief
         *
         */
        void _Interrupt() { splineflags.done = true; }

    public:

        /**
         * @brief
         *
         * @param
         */
        void Initialize(const MoveSplineInitArgs&);
        /**
         * @brief
         *
         * @return bool
         */
        bool Initialized() const { return !spline.empty(); }

        /**
         * @brief
         *
         */
        explicit MoveSpline();

        template<class UpdateHandler>
        /**
         * @brief
         *
         * @param difftime
         * @param handler
         */
        void updateState(int difftime, UpdateHandler& handler)
        {
            CFT_ASSERT(Initialized());
            do
            {
                handler(_updateState(difftime));
            } while (difftime > 0);
        }

        /**
         * @brief
         *
         * @param difftime
         */
        void updateState(int difftime)
        {
            CFT_ASSERT(Initialized());
            do { _updateState(difftime); } while (difftime > 0);
        }

        /**
         * @brief
         *
         * @return Location
         */
        Location ComputePosition() const;

        /**
         * @brief
         *
         * @return unsigned int
         */
        unsigned int GetId() const { return m_Id; }
        /**
         * @brief
         *
         * @return bool
         */
        bool Finalized() const { return splineflags.done; }
        /**
         * @brief
         *
         * @return bool
         */
        bool isCyclic() const { return splineflags.cyclic; }
        /**
         * @brief
         *
         * @return const Vector3
         */
        const Vector3 FinalDestination() const { return Initialized() ? spline.getPoint(spline.last()) : Vector3(); }
        /**
         * @brief
         *
         * @return const Vector3
         */
        const Vector3 CurrentDestination() const { return Initialized() ? spline.getPoint(point_Idx + 1) : Vector3(); }
        /**
         * @brief
         *
         * @return int
         */
        int currentPathIdx() const;

        /**
         * @brief
         *
         * @return int
         */
        int Duration() const { return spline.length(); }

        /**
         * @brief
         *
         * @return std::string
         */
        std::string ToString() const;
    };
}
